Skip to content

Conversation

@Jon-edge
Copy link
Collaborator

@Jon-edge Jon-edge commented Jan 16, 2026

edge/react-fc-component-definition & edge/react-render-function-definition

Two complementary ESLint rules enforcing consistent React component and render function typing conventions.

Rule Details

edge/react-fc-component-definition

Targets PascalCase-named arrow functions that return JSX types. Components should use React.FC<Props> instead of explicit return types.

edge/react-render-function-definition

Targets camelCase functions starting with render (e.g., renderItem, renderHeader). Render helpers should use React.ReactElement instead of ReactNode or JSX.Element.

Why These Rules?

For Components (React.FC<Props>):

  1. Consistency - Uniform component signatures across the codebase
  2. Implicit children - React.FC handles the children prop type automatically
  3. Cleaner syntax - Props type is declared once on the variable, not repeated
  4. Return type inference - The return type is inferred from React.FC, reducing verbosity

For Render Helpers (React.ReactElement):

  1. Type precision - ReactNode is too broad (includes strings, numbers, arrays)
  2. Consistency - JSX.Element should be ReactElement for uniformity
  3. Explicit unions - Nullable returns should use ReactElement | null

Examples

Components (PascalCase)

Incorrect

// Explicit return type on arrow function
export const MyComponent = (props: Props): React.ReactElement => {
  return <View>{props.title}</View>
}

// With nullable return
export const CountdownTile = (props: Props): React.ReactElement | null => {
  if (!props.show) return null
  return <View>{props.value}</View>
}

// Using JSX.Element
const Button = (props: ButtonProps): JSX.Element => {
  return <TouchableOpacity>{props.label}</TouchableOpacity>
}

Correct

// Type annotation on the variable
export const MyComponent: React.FC<Props> = props => {
  return <View>{props.title}</View>
}

// Nullable returns work with React.FC
export const CountdownTile: React.FC<Props> = props => {
  if (!props.show) return null
  return <View>{props.value}</View>
}

// Destructured props
const Button: React.FC<ButtonProps> = ({ label, onPress }) => {
  return <TouchableOpacity onPress={onPress}>{label}</TouchableOpacity>
}

Render Helpers (camelCase)

Incorrect

// ReactNode is too broad
const renderItem = (item: Item): React.ReactNode => {
  return <ItemRow item={item} />
}

// JSX.Element should be ReactElement
const renderHeader = (): JSX.Element => {
  return <Header title="My List" />
}

Correct

// Explicit ReactElement return type
const renderItem = (item: Item): React.ReactElement => {
  return <ItemRow item={item} />
}

// Nullable returns use explicit union
const renderBadge = (show: boolean): React.ReactElement | null => {
  if (!show) return null
  return <Badge />
}

// Wrap strings/arrays in fragments
const renderStatus = (loading: boolean): React.ReactElement => {
  if (loading) return <>Loading...</>
  return <StatusIcon />
}

Configuration

Both rules are enabled as warnings by default in the Edge ESLint configuration:

{
  rules: {
    'edge/react-fc-component-definition': 'warn',
    'edge/react-render-function-definition': 'warn'
  }
}

Related

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Requirements

If you have made any visual changes to the GUI. Make sure you have:

  • Tested on iOS device
  • Tested on Android device
  • Tested on small-screen device (iPod Touch)
  • Tested on large-screen device (tablet)

@Jon-edge Jon-edge mentioned this pull request Jan 16, 2026
6 tasks
Copy link
Contributor

@swansontec swansontec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will work, but I found two optional cleanups we could do.

Comment on lines 47 to 50
// Handle `null` in union types - not a JSX type itself
if (node.type === 'TSNullKeyword') {
return false
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block does nothing - we're going to return false either way.

}
// Handle qualified names like `React.ReactElement` or `React.JSX.Element`
if (typeName.type === 'TSQualifiedName') {
return getTypeName(typeName.left) + '.' + typeName.right.name
Copy link
Contributor

@swansontec swansontec Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are going to return strings like "React.ReactElement", should we be more explicit about putting full type names in the JSX_RETURN_TYPES array? I see that we use .endsWith to do the check, so we could alternatively just return typeName.right.name and skip the recursion, since we don't actually care what's on the left.

@Jon-edge Jon-edge force-pushed the jon/eslint-react-fc branch from d0645d5 to 805218f Compare January 16, 2026 20:34
@Jon-edge Jon-edge force-pushed the jon/eslint-react-fc branch from 805218f to 2988c9b Compare January 16, 2026 21:42
Enforces that render helper functions (camelCase functions starting with "render") use React.ReactElement return type instead of ReactNode or JSX.Element.

- Flags ReactNode (too broad - use ReactElement or explicit union)
- Flags JSX.Element (use ReactElement for consistency)
- Allows ReactElement, ReactElement | null, and other explicit unions
@Jon-edge
Copy link
Collaborator Author

Updated description, added one more rule

@Jon-edge Jon-edge changed the title Integrate eslint react-fc-component-definition Integrate eslint react-fc-component-definition, react-render-function-definition Jan 17, 2026
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

// Handle `React.ReactElement`, `React.JSX.Element`, etc.
if (node.type === 'TSTypeReference') {
const typeName = getTypeName(node.typeName)
return JSX_RETURN_TYPES.some(t => typeName.endsWith(t))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type matching incorrectly flags non-JSX types ending with "Element"

Low Severity

The .endsWith() check combined with 'Element' in JSX_RETURN_TYPES causes false positives. Since getTypeName returns only the rightmost identifier (e.g., 'HTMLElement' for the type HTMLElement), the check 'HTMLElement'.endsWith('Element') evaluates to true. This incorrectly flags PascalCase functions returning HTMLElement, SVGElement, or any custom type ending with Element as needing React.FC<Props>. The second rule correctly uses .includes() for exact matching, but this rule's .endsWith() approach is too broad.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants